import requests,os,re,execjs,time
from Crypto.Cipher import AES

class Spider():
    def __init__(self,url):
        # url: 待解析视频链接
        # save_path: 视频保存目录
        # video_name: 视频文件夹名称，会根据爬取到的视频自动更改

        self.url = url
        self.save_path = './'
        self.video_name = 'video'
        self.key = ''   
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
            'Referer': 'https://z1.m1907.cn/',
        }

    def get_params(self):
        # 运行js获取访问参数，经过分析发现g是根据cookie来获取的，可以直接置为空
        with open('./api.js',mode='r',encoding='utf-8') as f:
            ctx = execjs.compile(f.read())
        z,s1ig,g = ctx.call('get_z')
        return {'z':z, 'jx':self.url, 's1ig':s1ig, 'g':g}

    def get_m3u8(self):
        # 获取m3u8文件列表，通过download方法下载
        url = f'https://z1.m1907.cn/api/v/?'
        params = self.get_params()
        js = requests.get(url,headers=self.headers,params=params).json()
        m3u8_list = js['data'][0]['source']['eps']
        self.video_name = js['data'][0]['name']
        for i in m3u8_list:     # 默认是对优酷视频链接所在电视剧或动漫的所有集数都进行下载，可以通过下标或者切片选择要下载哪一集
            url = i['url']
            pre_url = re.findall('(https://.+?.cn)',url)[0]
            rsp = requests.get(url,headers=self.headers).text
            m3u8_url = pre_url + re.findall(r'(/.*\.m3u8)',rsp)[0]
            print(m3u8_url)
            self.download(i['name'],m3u8_url)

    def download(self,name,m3u8_url):
        # 根据m3u8获取AES加密密码key和ts文件列表，下载ts文件存入数组contents
        m3u8 = requests.get(m3u8_url,headers=self.headers).text
        video_list = re.findall(r'(https://.+?\.ts)',m3u8)
        url = re.findall(r'URI="(.+?)"',m3u8)[0]
        self.key = requests.get(url).content

        print('key:',self.key)

        path = self.save_path + self.video_name + '/'
        if (not os.path.exists(path)):
            os.mkdir(path)

        print(f'downloading:{name}')
        
        contents = []
        for url in video_list:
            while True:
                try:
                    print(f'\t{url}')
                    contents.append(requests.get(url,headers=self.headers,timeout=15).content)
                    break
                except Exception as e:
                    print(f'{type(e)}:{e}')
                    time.sleep(3)

        self.save(path,name,contents)

    def save(self,path,name,contents):
        # 将contents数组中存储的ts文件数据写入到.mp4文件，ts文件经过AES加密，通过之前获取到的key进行解密
        print('{name}下载完成，保存中...')
        crypto = AES.new(self.key,AES.MODE_CBC,b'0000000000000000')
        with open(path + name + '.mp4','ab+') as f:
            for content in contents:
                #   报错：Data must be padded to 16 byte boundary in CBC mode 
                #   获取到的ts文件有部分长度不为16的倍数，进行解密会报错
                #   对于长度不为16倍数的文件去除掉多余的部分
                x = len(content)%16
                if(x==0): f.write(crypto.decrypt(content))
                else: f.write(crypto.decrypt(content[:-x]))

if __name__ == '__main__':
    url = 'https://v.youku.com/v_show/id_XNjU1NDE5NjIw.html?spm=a2hbt.13141534.app.5~5!2~5!2~5~5~5!2~5~5!2~5!2~5!2~5~A!2&s=33ea967a5ca311e3b8b7'    # 优酷的视频链接
    spider = Spider(url)
    spider.get_m3u8()

# %%